#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
#include <string.h>
#include "../Headers/winbufADT.h"

#define CHAT_PROMPT_HEIGHT 3
#define USER_NAME_LEN 20

/* Trata al ENTER como un caracter cualquiera, esa logica debe estar
 * del lado de quien usa el ADT.
 * Ignora los TABS.
 */

struct winbufCDT{
    int t_size;     /* Define el maximo numero de caracteres que soporta el buf */
    int p_height;   /* Define la altura de la ventana */
    int p_width;
    char ** buffer;
    int r_index;    /* Definen las coordenadas actuales del buffer */
    int c_index;
    int count;
    int user_count;
    int user_row;
    
    /* valores necesarios para ncurses */
    int starty;
    int startx;
    WINDOW * prompt_win;
    WINDOW * users_win;
};

/* ------------------------------------------------------------------------- */
/* -------------------- Funciones Auxiliares ------------------------------- */
/* ------------------------------------------------------------------------- */

int getWBRows (int t_size, int p_height, int p_width);

void scrollWB (winbufADT wb);

void eraseWB (winbufADT wb);

/* Funciones para manipulacion de windows */
/* http://www.tldp.org/HOWTO/NCURSES-Programming-HOWTO/windows.html */

WINDOW *create_newwin(int height, int width, int starty, int startx);
void destroy_win(WINDOW *local_win);

/* ------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------- */

/* p_height es la cantidad de filas que se ven en pantalla en simultaneo
 * p_width es la cantidad de columnas que se ven en pantalla en simultaneo
 * rows es la cantidad de filas real q va a tener el buffer
 */
 
winbufADT
NewWB (int t_size)
{
    int i, j;
    int p_height, p_width, starty, startx;
    int u_height, u_width, ustarty, ustartx;
    int rows;
    WINDOW * prompt_win, * users_win;
    winbufADT wb;
        
    if ((wb = malloc (sizeof (struct winbufCDT))) == NULL){
        return NULL;
    }
    
    /* Inicio ncurses */
    initscr();
    cbreak();
    //raw();
    keypad(stdscr, TRUE);
    refresh();    
    noecho (); 
    halfdelay (1);
    scrollok(stdscr, TRUE);
    idlok(stdscr, TRUE);
    start_color();
    
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);
    init_pair(3, COLOR_MAGENTA, COLOR_BLACK);
    init_pair(4, COLOR_BLUE, COLOR_BLACK);
    
    /* Datos para la ventana donde escribe el usuario */
    p_height = CHAT_PROMPT_HEIGHT;
    p_width = COLS;
    starty = LINES - CHAT_PROMPT_HEIGHT;
    startx = 0;
    
    /* Datos para la ventana donde se listan los usuarios */
    u_height = LINES - CHAT_PROMPT_HEIGHT;
    u_width = USER_NAME_LEN;
    ustarty = 0;
    ustartx = COLS - USER_NAME_LEN;
    
    
    /* Guardo valores en la estructura */
    wb->t_size = t_size;
    wb->p_height = p_height-2;  /* Le resto 2 por los "dibujos" */
    wb->p_width = p_width-2;    /* Le resto 2 por los "dibujos" */
    wb->r_index = 0;
    wb->c_index = 0;
    wb->count = 0;
    wb->starty = starty;
    wb->startx = startx;
    wb->user_count = 0;
    wb->user_row = 2;
        
    /* Reservo memoria suficiente para todo el buffer */
    if ((wb->buffer = malloc (sizeof (char *) * wb->p_height)) == NULL){
        free (wb);
        return NULL;
    }
    for (i=0 ; i < wb->p_height ; i++){
        if ((wb->buffer[i] = malloc (sizeof (int) * wb->p_width)) == NULL){
            /* libero todos los anteriores */
            free (wb->buffer);
            free (wb);
            return NULL;
        }
    }
    
    /* Creo las ventanas */
    prompt_win = create_newwin (p_height, p_width, starty, startx);
    users_win = create_newwin (u_height, u_width, ustarty, ustartx);
    /* Dejo el cursor seteado en la posicion (1,1) */
    wmove (prompt_win, 1, 1);
    
    /* Hago un resize de la pantalla donde llegan los mensajes */
    if (wresize(stdscr, LINES - CHAT_PROMPT_HEIGHT, COLS - USER_NAME_LEN) == ERR)
        fprintf (stdout, "Resize error\n");
    
    wrefresh (prompt_win);
    wb->prompt_win = prompt_win; 
    wb->users_win = users_win;
    
    return wb;
}

int
getWBCh(winbufADT wb)
{
    return getch();   
}

int 
putWBChar (winbufADT wb, int ch, int win_type)
{
	int prompt_len = strlen ("cmmd:> ");
	WINDOW * win;
	if (win_type == PROMPT_WIN)
		win = wb->prompt_win;
	else if (win_type == MSGS_WIN)
		win = stdscr;
	else 
		return 0;
	
    if (ch == KEY_BACKSPACE || ch == 127){
        /* Considero el prompt */
    	if (wb->r_index == 0 && wb->c_index <= prompt_len)
    		return -1;
        if (wb->c_index == 0){
            /* Si estoy contra la izquierda pero tengo un renglon arriba,
             * borro el caracter del renglon de arriba */
            if (wb->r_index > 0){
                wb->r_index -= 1;
                wb->c_index = wb->p_width-1;
                wb->count -= 1;
                wb->buffer[wb->r_index][wb->c_index] = ' ';
                /* Imprimo en pantalla lo mismo q en el buffer */
                ch = ' ';
                mvwprintw (win, wb->r_index + 1, wb->c_index + 1, "%c", ch);
                wmove (win, wb->r_index + 1, wb->c_index + 1);
                //wprintw (win, "%c", ch);
                wrefresh (win);
            }
            /* Si estoy en el borde superior izquierdo ignoro el BACKSPACE */
            else return -1;
        }
        else{
            /* Sino pongo un blanco en el lugar anterior y actualizo indice */
            wb->c_index -= 1;
            wb->count -= 1;
            wb->buffer[wb->r_index][wb->c_index] = ' ';
            /* Imprimo en pantalla lo mismo q en el buffer */
            ch = ' ';
            mvwprintw (win, wb->r_index + 1, wb->c_index + 1, "%c", ch);
            wmove (win, wb->r_index + 1, wb->c_index + 1);
            wrefresh (win);
        }
    }
    else
    {/* El resto de los caracteres */
        
        /* Chequeo no haber superado el tamanio maximo pedido
         * por el usuario */         
        if (wb->count >= wb->t_size - 1){
            return -3;
        }
        
        else if (wb->c_index == wb->p_width-1){
            /* Si estoy al final de una linea del buffer */
             if (wb->r_index < wb->p_height-1){
                wb->r_index += 1;
                wb->c_index = 0;
                wb->count += 1;
                wb->buffer[wb->r_index][wb->c_index] = ch;

                /* Imprimo en pantalla lo mismo q en el buffer */
                mvwprintw (win, wb->r_index + 1 , wb->c_index + 1, "%c", ch);
                wrefresh (win);                
                

                
                /* Scrolleo lo que se ve en pantalla */
                //wb->p_r_start += 1;
                //scrollWB (wb);
                
             }
             /* Si estoy al final de todo el buffer ignoro */
             else return -2;
        }
        else{
        	wb->count += 1;
            wb->buffer[wb->r_index][wb->c_index] = ch;
            /* Imprimo en pantalla lo mismo q en el buffer */
            mvwprintw (win, wb->r_index + 1 , wb->c_index + 1, "%c", ch);
            wrefresh (win);   
               
            /* Muevo el cursor del buffer */
            wb->c_index += 1;      
        }
    }
    
    return 1;
}

int
putWBStr (winbufADT wb, char * str, int win_type, int opt)
{
    int prompt_len = strlen ("cmmd:> ");
	WINDOW * win;
    
	if (win_type == PROMPT_WIN)
		win = wb->prompt_win;
	else if (win_type == MSGS_WIN)
		win = stdscr;
    else if (win_type == USERS_WIN){
        win = wb->users_win;
        wmove (win, wb->user_row , wb->user_count + 1);
        //wb->user_count += 1;
        wb->user_row +=1;
    } 
	else 
		return 0;
	
    wprintw (win, "%s", str);
    
    if (opt == ENTER)
    	wprintw (win, "\n");
        
    /* Si es en PROMPT_WIN era una string de prompt, "adelanto" el cursor */
    if (win_type == PROMPT_WIN)
    {
        wb->r_index = 0;
        wb->c_index = prompt_len;
        wmove (wb->prompt_win, 1, prompt_len+1);
    }
    
    /* Si era userswin hago un retoque */
    if (win_type == USERS_WIN)
    {
        box(win, 0 , 0);
    }
    
    wrefresh (win);
    
	return 1;
}

static char *
getWB (winbufADT wb, int * size)
{
    char * ans;
    int i, j, index = 0;
    
    if ((ans = calloc (sizeof(char), wb->t_size * 2) ) == NULL){
        *size = -1;
        return NULL;
    }
    
    /* Devuelvo en size el tamanio del buffer q se devuelve */
    *size = wb->count + 2;
    
    /* Copio el buffer a ans para devolver */
    for (i=0 ; i < wb->p_height ; i++){
        for (j=0 ; j<wb->p_width ; j++){
            ans[index++] = wb->buffer[i][j];
        }
    }
    
    return ans;
}

char *
getAndEraseWB (winbufADT wb, int * size)
{
    char * ans;
    ans = getWB (wb, size);
    
    eraseWB (wb);
    
    wmove (wb->prompt_win, 1, 1);
    wrefresh (wb->prompt_win); 
    wb->r_index = 0;
    wb->c_index = 0;
    wb->count = 0;
    
    return ans;
}

void 
WBColorOn (winbufADT wb, int group, int opt)
{
    WINDOW * win;
    
    if (opt == MSGS_WIN)
        win = stdscr;
    else if (opt == USERS_WIN)
        win = wb->users_win;
    else if (opt == PROMPT_WIN)
        win = wb->prompt_win;
    else return ;
    
	wattron(win, COLOR_PAIR(group));
}

void 
WBColorOff (winbufADT wb, int group, int opt)
{
    WINDOW * win;
    
    if (opt == MSGS_WIN)
        win = stdscr;
    else if (opt == USERS_WIN)
        win = wb->users_win;
    else if (opt == PROMPT_WIN)
        win = wb->prompt_win;
    else return ;
    
	wattroff(win, COLOR_PAIR(group));
}

void
WBRowRest (winbufADT wb)
{
    wb->user_row = 2;
}

void FreeWB (winbufADT wb)
{
    int i;
    for (i=0; i < wb->p_height; i++){
        free (wb->buffer[i]);
    }
    free (wb->buffer);
    delwin(wb->prompt_win);
    delwin(wb->users_win);
    free (wb);
    endwin();
    return ;
}

/* ------------------------------------------------------------------------- */
/* -------------------- Funciones Auxiliares ------------------------------- */
/* ------------------------------------------------------------------------- */

void
eraseWB (winbufADT wb)
{
    int i, j;
    int ch = ' ';
    
    for (i=0 ; i < wb->p_height ; i++){
        for (j=0 ; j < wb->p_width; j++){
            wb->buffer[i][j] = 0;
            mvwprintw (wb->prompt_win, i + 1 , j + 1, "%c", ch);
        }
    }
    /* Dejo el cursor seteado en la posicion (1,1) */
    wmove (wb->prompt_win, 1, 1);
    wrefresh (wb->prompt_win);
}


WINDOW *
create_newwin(int height, int width, int starty, int startx)
{   WINDOW *local_win;

    local_win = newwin(height, width, starty, startx);
    box(local_win, 0 , 0);      /* 0, 0 gives default characters 
                     * for the vertical and horizontal
                     * lines            */
    wrefresh(local_win);        /* Show that box        */

    return local_win;
}

void destroy_win(WINDOW *local_win)
{   
    /* box(local_win, ' ', ' '); : This won't produce the desired
     * result of erasing the window. It will leave it's four corners 
     * and so an ugly remnant of window. 
     */
    wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' ');
    /* The parameters taken are 
     * 1. win: the window on which to operate
     * 2. ls: character to be used for the left side of the window 
     * 3. rs: character to be used for the right side of the window 
     * 4. ts: character to be used for the top side of the window 
     * 5. bs: character to be used for the bottom side of the window 
     * 6. tl: character to be used for the top left corner of the window 
     * 7. tr: character to be used for the top right corner of the window 
     * 8. bl: character to be used for the bottom left corner of the window 
     * 9. br: character to be used for the bottom right corner of the window
     */
    wrefresh(local_win);
    delwin(local_win);
}
